use super::{
    element::{Card, Cards, DecidingType, Player, PlayerId, PlayerState, CardType},
    NamedEnum,
};
use std::{collections::HashMap, hash::Hash};
use tracing::debug;

#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
pub enum State {
    Init,
    Ready,
    Dealing,
    DingQueing,
    Deciding,
    Accounting,
}

type StateName = String;
impl NamedEnum for State {
    fn name(&self) -> StateName {
        match self {
            State::Init => String::from("Init"),
            State::Ready => String::from("Ready"),
            State::Dealing => String::from("Dealing"),
            State::DingQueing => String::from("DingQueing"),
            State::Deciding => String::from("Deciding"),
            State::Accounting => String::from("Accounting"),
        }
    }
}

#[derive(Hash, PartialEq, Eq, Clone, Copy)]
pub enum Event {
    Ready(PlayerId),
    DeReady(PlayerId),
    SitDown(PlayerId),
    StandUp(PlayerId),
    Start(PlayerId),
    DingQue(PlayerId, CardType),
    Da(PlayerId, Card),
    Peng(PlayerId, Card),
    Gang(PlayerId, Card),
    Hu(PlayerId, Card),
    Skip(PlayerId, Card),
}

type EventName = String;
impl NamedEnum for Event {
    fn name(&self) -> EventName {
        match self {
            Event::Ready(_) => String::from("Ready"),
            Event::DeReady(_) => String::from("DeReady"),
            Event::SitDown(_) => String::from("SitDown"),
            Event::StandUp(_) => String::from("StandUp"),
            Event::Start(_) => String::from("Start"),
            Event::DingQue(_, _) => String::from("DingQue"),
            Event::Da(_, _) => String::from("Da"),
            Event::Peng(_, _) => String::from("Peng"),
            Event::Gang(_, _) => String::from("Gang"),
            Event::Hu(_, _) => String::from("Hu"),
            Event::Skip(_, _) => String::from("Skip"),
        }
    }
}

pub enum Action {}

type Checker = fn(&Machine, Event) -> bool;
type Actioner = fn(&mut Machine, Event);

#[derive(Hash, PartialEq, Eq)]
struct From(StateName, EventName);
struct To(State, Checker, Actioner);

pub struct Machine<'a> {
    id: u32,
    state: State,
    players: Vec<Player>,
    active_player_index: usize,
    cards: Cards,
    owner_id: u8,
    min_player_quantity: u8,
    max_player_quantity: u8,
    trans: &'a HashMap<From, Vec<To>>,
}

impl<'a> Machine<'a> {
    pub fn fire(&mut self, e: Event) {
        let op = self.trans.get(&From(self.state.name(), e.name()));

        if let Some(trans) = op {
            let mut trans_log = self.state.name();

            for tran in trans.iter() {
                let (to_state, check, action) = (tran.0, tran.1, tran.2);
                if check(self, e.clone()) {
                    trans_log.push_str(" --> ");
                    trans_log.push_str(to_state.name().as_str());
                    self.state = to_state;
                    action(self, e);
                    continue;
                }

                break;
            }

            debug!("{}", trans_log);

            return;
        }

        debug!("Not matched event: {}", e.name())
    }

    pub fn could_sitdown(m: &Machine<'_>, e: Event) -> bool {
        if let Event::SitDown(id) = e {
            let exist = m.players.iter().find(|p| p.id == id).is_some();

            if exist {
                debug!("Player sat");
                return false;
            }

            if m.players.len() >= m.max_player_quantity as usize {
                debug!("Desk can't accommodate more players");
                return false;
            }

            return true;
        }

        false
    }

    pub fn sitdown(m: &mut Machine<'_>, e: Event) {
        if let Event::SitDown(id) = e {
            m.players.push(Player::with_id(id))
        }
    }

    pub fn is_ready(m: &Machine<'_>, e: Event) -> bool {
        if m.players.len() < m.min_player_quantity as usize {
            return false;
        }
  
        !m.players
            .iter()
            .find(|p| p.state != PlayerState::Ready)
            .is_some()
    }

    pub fn could_standup(m: &Machine<'_>, e: Event) -> bool {
        if let Event::StandUp(id) = e {
            return m.players.iter().find(|p| p.id == id).is_some();
        }
        false
    }

    pub fn stand_up(m: &mut Machine<'_>, e: Event) {
        if let Event::StandUp(id) = e {
            m.players.retain(|p| p.id != id);
        }
    }

    pub fn is_not_ready(m: &Machine<'_>, e: Event) -> bool {
        return !Machine::is_ready(m, e);
    }

    pub fn deal(m: &mut Machine<'_>, e: Event) {
        let do_deal = |p: &mut Player| {
            for _ in 0..13 {
                p.hand_cards.push(m.cards.pop());
            }
        };

        m.players.iter_mut().for_each(do_deal);

        m.players[m.active_player_index]
            .hand_cards
            .push(m.cards.pop());
    }

    pub fn could_deready(m: &Machine<'_>, e: Event) -> bool {
        true
    }

    pub fn ready(m: &mut Machine<'_>, e: Event) {}

    pub fn deready(m: &mut Machine<'_>, e: Event) {}

    pub fn start_dingque(_: &mut Machine<'_>, _: Event) {}

    pub fn is_valid_dingque(m: &Machine<'_>, e: Event) -> bool {
        if let Event::DingQue(id, _) = e {
            return match m.players.iter().find(|p| p.id == id) {
                Some(p) => p.que == CardType::None,
                None => false,
            };
        }

        false
    }

    pub fn dingque(m: &mut Machine<'_>, e: Event) {
        if let Event::DingQue(id, card) = e {
            m.players.iter_mut().find(|p| p.id == id).unwrap().que = card;
        }
    }

    pub fn all_dingqued(m: &Machine<'_>, _: Event) -> bool {
        m.players.iter().find(|p| p.que == CardType::None).is_none()
    }

    pub fn start_deciding(m: &mut Machine<'_>, _: Event) {
        let p = &mut m.players[m.active_player_index];
        p.state = PlayerState::Deciding;
        p.decidings.push(DecidingType::Da);

        //TODO check zimo & an gang
    }

    pub fn allways_true(m: &Machine<'_>, e: Event) -> bool {
        true
    }

    pub fn do_nothing(m: &mut Machine<'_>, e: Event) {}
}

pub struct MachineManager {
    trans: HashMap<From, Vec<To>>,
}

impl MachineManager {
    pub fn new() -> Self {
        let mut t = HashMap::new();

        {
            // origin state is 'Init'
            t.insert(
                From(State::Init.name(), Event::SitDown(0).name()),
                vec![
                    To(State::Init, Machine::could_sitdown, Machine::sitdown),
                    To(State::Ready, Machine::is_ready, Machine::do_nothing),
                ],
            );
        }

        {
            // origin state is 'Ready'
            t.insert(
                From(State::Ready.name(), Event::SitDown(0).name()),
                vec![To(State::Ready, Machine::could_sitdown, Machine::sitdown)],
            );

            t.insert(
                From(State::Ready.name(), Event::StandUp(0).name()),
                vec![
                    To(State::Ready, Machine::could_standup, Machine::stand_up),
                    To(State::Init, Machine::is_not_ready, Machine::do_nothing),
                ],
            );

            t.insert(
                From(State::Ready.name(), Event::Start(0).name()),
                vec![
                    To(State::Dealing, Machine::allways_true, Machine::deal),
                    To(
                        State::DingQueing,
                        Machine::allways_true,
                        Machine::start_dingque,
                    ),
                ],
            );

            t.insert(
                From(State::Ready.name(), Event::Ready(0).name()),
                vec![To(State::Ready, Machine::allways_true, Machine::ready)],
            );

            t.insert(
                From(State::Ready.name(), Event::DeReady(0).name()),
                vec![To(State::Init, Machine::could_deready, Machine::deready)],
            );
        }

        {
            // origin state is 'DingQueing'
            t.insert(
                From(
                    State::DingQueing.name(),
                    Event::DingQue(0, CardType::None).name(),
                ),
                vec![
                    To(
                        State::DingQueing,
                        Machine::is_valid_dingque,
                        Machine::dingque,
                    ),
                    To(
                        State::Deciding,
                        Machine::all_dingqued,
                        Machine::start_deciding,
                    ),
                ],
            );
        }

        {
            // origin state is 'Deciding'
        }

        MachineManager { trans: t }
    }

    pub fn default(&self) -> Machine {
        Machine {
            id: 0,
            state: State::Init,
            players: Vec::new(),
            active_player_index: 0,
            cards: Cards::new_108(),
            min_player_quantity: 3,
            max_player_quantity: 4,
            owner_id: 0,
            trans: &self.trans,
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Event, MachineManager, State};

    #[test]
    fn sitdown_standup() {
        let mm = MachineManager::new();
        let mut m = mm.default();
        m.min_player_quantity = 3;
        m.max_player_quantity = 4;
        m.fire(Event::SitDown(1));
        assert_eq!(m.state, State::Init);
        m.fire(Event::SitDown(2));
        assert_eq!(m.state, State::Init);
        m.fire(Event::SitDown(2));
        assert_eq!(m.state, State::Init);
        m.fire(Event::SitDown(3));
        assert_eq!(m.state, State::Ready);
        m.fire(Event::SitDown(4));
        assert_eq!(m.state, State::Ready);
        m.fire(Event::StandUp(1));
        assert_eq!(m.state, State::Ready);
        m.fire(Event::StandUp(2));
        assert_eq!(m.state, State::Init);
    }

    #[test]
    fn deal() {
        let mm = MachineManager::new();
        let mut m = mm.default();
        m.cards.shuffle();
        m.min_player_quantity = 3;
        m.max_player_quantity = 4;
        m.fire(Event::SitDown(1));
        m.fire(Event::SitDown(2));
        m.fire(Event::SitDown(3));

        m.fire(Event::Start(1));
        assert_eq!(m.state, State::DingQueing);
        for p in m.players {
            assert!(p.hand_cards.len() >= 13);
            println!("{:?}", p.hand_cards);
        }
    }
}
